#pragma once
#include "../texture2d.hpp"

namespace opengl {

bool texture2d::auto_generate_mipmaps = true;

OPENGLAPI texture2d::texture2d() : id(0) {}

/* Constructors */
OPENGLAPI texture2d::texture2d(const image2d &image, u32 wrappingX, u32 wrappingY, u32 min_filter, u32 mag_filter,
                               i32 mipmap_levels)
    : id(0) {
    create(image, wrappingX, wrappingY, min_filter, mag_filter, mipmap_levels);
}
OPENGLAPI texture2d::texture2d(texture2d &&other) : id(other.id) { other.id = 0; }

OPENGLAPI void texture2d::create(const image2d &image, u32 wrappingX, u32 wrappingY, u32 min_filter, u32 mag_filter,
                                 i32 mipmap_levels) {
    u32 image_data_type = OPENGL_UNSIGNED_BYTE, image_format = OPENGL_RGB;
    switch (image.format) {
        case format_r8:
            image_format = OPENGL_RED;
            image_data_type = OPENGL_UNSIGNED_BYTE;
            break;
        case format_rg8:
            image_format = OPENGL_RG;
            image_data_type = OPENGL_UNSIGNED_BYTE;
            break;
        case format_rgb8:
            image_format = OPENGL_RGB;
            image_data_type = OPENGL_UNSIGNED_BYTE;
            break;
        case format_rgba8:
            image_format = OPENGL_RGBA;
            image_data_type = OPENGL_UNSIGNED_BYTE;
            break;
        case format_r32f:
            image_format = OPENGL_RED;
            image_data_type = OPENGL_FLOAT;
            break;
        case format_rg32f:
            image_format = OPENGL_RG;
            image_data_type = OPENGL_FLOAT;
            break;
        case format_rgb32f:
            image_format = OPENGL_RGB;
            image_data_type = OPENGL_FLOAT;
            break;
        case format_rgba32f:
            image_format = OPENGL_RGBA;
            image_data_type = OPENGL_FLOAT;
            break;
        default:
            break;
    }
    api::glCreateTextures(0x0DE1, 1, &id); // GL_TEXTURE_2D
    api::glTextureStorage2D(id, mipmap_levels, image.format, image.width, image.height);
    api::glTextureParameteri(id, OPENGL_TEXTURE_WRAP_S, wrappingX);
    api::glTextureParameteri(id, OPENGL_TEXTURE_WRAP_T, wrappingY);
    api::glTextureParameteri(id, OPENGL_TEXTURE_MIN_FILTER, min_filter);
    api::glTextureParameteri(id, OPENGL_TEXTURE_MAG_FILTER, mag_filter);
    if (image.data != nullptr) {
        api::glTextureSubImage2D(id, 0, 0, 0, image.width, image.height, image_format, image_data_type, image.data);
        if (texture2d::auto_generate_mipmaps) {
            generate_mipmap();
        }
    }
}

/* Member functions */
OPENGLAPI void texture2d::set_data(const void *data, u32 width, u32 height, u32 format, i32 level) const {
    api::glTextureSubImage2D(id, level, 0, 0, width, height, format, 0x1401, data); // GL_UNSIGNED_BYTE
    if (texture2d::auto_generate_mipmaps) {
        generate_mipmap();
    }
}
OPENGLAPI void texture2d::generate_mipmap() const { api::glGenerateTextureMipmap(id); }
OPENGLAPI void texture2d::set_wrapping(u32 wrappingX, u32 wrappingY) const {
    api::glTextureParameteri(id, OPENGL_TEXTURE_WRAP_S, wrappingX);
    api::glTextureParameteri(id, OPENGL_TEXTURE_WRAP_T, wrappingY);
}
OPENGLAPI void texture2d::set_filter(u32 min_filter, u32 mag_filter) const {
    api::glTextureParameteri(id, OPENGL_TEXTURE_MIN_FILTER, min_filter);
    api::glTextureParameteri(id, OPENGL_TEXTURE_MAG_FILTER, mag_filter);
}
OPENGLAPI void texture2d::bind_to(u32 unit) const { api::glBindTextureUnit(unit, id); }

/* Destructors */
OPENGLAPI texture2d::~texture2d() {
    if (id != 0) {
        api::glDeleteTextures(1, &id);
        id = 0;
    }
}

} // namespace opengl